//******************************************************************************
//this code is protected by the GNU affero GPLv3
//author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
//                        <digital.ragnarok AT gmail dot com>
//******************************************************************************
#define WAYLAND_C
#define SRV_C
#include "globals.h"
#undef SRV_C
#undef WAYLAND_C

//------------------------------------------------------------------------------
//misc stuff
//------------------------------------------------------------------------------
static i ep_fd;//the server epoll fd
#define SIGBIT(sig) (1<<(sig-1))
static i sigs_fd;//*non blocking* socket
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//server socket stuff
//------------------------------------------------------------------------------
static i srv_so;//*non blocking* socket for accept behaviour
static struct sockaddr_un srv_addr={
  .sun_family=AF_UNIX,
  .sun_path[0]=0
};
//------------------------------------------------------------------------------

static void sigs_setup(void)
{
  //cannot change SIGKILL, neither SIGSTOP
  u64 mask=(~0);
  l r=rt_sigprocmask(SIG_BLOCK,&mask,0,sizeof(mask));
  if(ISERR(r)){
    PERR("fatal(%ld):unable to block mostly all signals\n",r);
    exit(LWL_ERR);
  }

  mask=SIGBIT(SIGTERM);
  sigs_fd=(i)signalfd4(-1,&mask,sizeof(mask),SFD_NONBLOCK);
  if(ISERR(sigs_fd)){
    PERR("fatal(%d):error setting up fd for signals\n",sigs_fd);
    exit(LWL_ERR);
  }
}

static void epoll_setup(void)
{
  ep_fd=(i)epoll_create1(EPOLL_CLOEXEC);
  if(ISERR(ep_fd)){
    PERR("fatal(%d):unable to create epoll fd\n",ep_fd);
    exit(LWL_ERR);
  }

  struct epoll_event ep_evt;

  memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=EPOLLET|EPOLLIN;//edge triggered
  ep_evt.data._32=EPOLL_SIGS;
  l r=epoll_ctl(ep_fd,EPOLL_CTL_ADD,sigs_fd,&ep_evt);
  if(ISERR(r)){
    PERR("fatal(%ld):unable to register signal fd to epoll\n",r);
    exit(LWL_ERR);
  }

  memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=EPOLLIN;//level triggered
  ep_evt.data._32=EPOLL_SRV;

  r=epoll_ctl(ep_fd,EPOLL_CTL_ADD,srv_so,&ep_evt);
  if(ISERR(r)){
    PERR("fatal(%ld):unable to register server socket to epoll\n",r);
    exit(LWL_ERR);
  }
}

static void *getenv(void *c,l argc,void **vp)
{
  vp+=argc+1;
  while(1){
    if(*vp==0) break;
    u8 *equal=*vp;
    while(1){if(*equal++=='=') break;}
    if(!strncmp(c,*vp,equal-1-(u8*)*vp)) return equal;
    ++vp;
  }
  return 0;
}

static void srv_cfg_file(l argc,void **argv_envp)
{
  void *lwl_conf=getenv("LWL_CONF",argc,argv_envp);
  if(!lwl_conf) return;

  g_cfg_path=lwl_conf;
}

static void srv_so_setup(l argc,void **argv_envp)
{
  srv_so=(i)socket(PF_UNIX,SOCK_NONBLOCK|SOCK_STREAM|SO_CLOEXEC,0);
  if(ISERR(srv_so)){
    PERR("fatal(%d):unable to create server *non blocking* socket\n",srv_so);
    exit(LWL_ERR);
  }

  void *xdg_dir=getenv("XDG_RUNTIME_DIR",argc,argv_envp);
  if(!xdg_dir){
    PERRC("fatal:missing environment variable XDG_RUNTIME_DIR\n");
    exit(LWL_ERR);
  }

  void *wl_disp=getenv("WAYLAND_DISPLAY",argc,argv_envp);
  if(!wl_disp){
    PERRC("fatal:missing environment variable WAYLAND_DISPLAY\n");
    exit(LWL_ERR);
  }

  //XXX:only ASCII compatible charsets for names
  u64 len=snprintf(&srv_addr.sun_path[0],UNIX_PATH_MAX,"%s/%s",xdg_dir,wl_disp);
  if(len==UNIX_PATH_MAX){
    PERR("fatal:%s/%s too big for a unix socket name\n",xdg_dir,wl_disp);
    exit(LWL_ERR);
  }
  
  l r=bind(srv_so,&srv_addr,sizeof(srv_addr));
  if(ISERR(r)){
    PERR("fatal(%ld):unable to bind server address name %s\n",r,
                                                             srv_addr.sun_path);
    exit(LWL_ERR);
  }

  r=listen(srv_so,0);
  if(ISERR(r)){
    PERR("fatal(%ld):unable to flag server listening socket\n",r);
    exit(LWL_ERR);
  }
}

static void conns_consume(void)
{
  while(1){
    l r;
    struct sockaddr_un client;
    l client_len;
    while(1){
      client.sun_family=AF_UNIX;
      client.sun_path[0]=0;
      client_len=sizeof(client);
      r=accept(srv_so,&client,&client_len);
      if(r!=-EINTR&&r!=-ECONNABORTED) break;
    }

    if(r!=-EAGAIN&&ISERR(r)){
      PERR("fatal(%ld):error accepting connection\n",r);
      exit(LWL_ERR);
    }
    if(client_len>(l)sizeof(client)){
      PERR("fatal(%ld):error accepting connection(socket address size)\n",r);
      exit(LWL_ERR);
    }

    if(r==-EAGAIN) break;//no more connection pending

    //client socket is a blocking socket
    
    r=(l)client_new((i)r);
    if(r!=0){
      PERR("WARNING:unable to create new client for connexion(%d)\n",(i)r);
      while(1){r=close((i)r); if(r!=-EINTR) break;}
    }
  }
}

//normal exit, we clean our hw resources
static void exit_ok(void)
{
  dce_outputs_cleanup_force();
  exit(LWL_OK);
}

static void sigs_consume(void)
{
  static struct signalfd_siginfo info;

  while(1){
    l r;
    while(1){
      memset(&info,0,sizeof(info));
      r=read(sigs_fd,&info,sizeof(info));
      if(r!=-EINTR) break;
    }
    if(r!=-EAGAIN&&((ISERR(r)||(r>0&&r!=sizeof(info))))){
      PERR("fatal(%ld):error consuming signals\n",r);
      exit(LWL_ERR);
    }
    if(r==0||r==-EAGAIN) break;

    switch(info.ssi_signo){
    case SIGTERM:
      exit_ok();
    }
  }
}

static void loop_sigs(struct epoll_event *e)
{
  if(e->events&EPOLLIN){
    sigs_consume();
    return;
  }
  PERR("fatal:something wrong with signal fd epolling events=%u\n",e->events);
  exit(LWL_ERR);
}

static void loop_srv(struct epoll_event *e)
{
  if(e->events&(EPOLLERR|EPOLLHUP)){
    PERR("fatal:something wrong with server socket epolling events=%u\n",
                                                                     e->events);
    exit(LWL_ERR);
  }else if(e->events&EPOLLIN){
    conns_consume();
    return;
  }
  PERR("fatal:unknown epolling event on server socket events=%u\n",e->events);
  exit(LWL_ERR);
}

static void loop_evdev(struct epoll_event *e)
{
  struct evdev *d=&evdevs[e->data._32-CLIENTS_MAX];
  if(e->events&(EPOLLERR|EPOLLHUP)){
    LOG_EVDEV("loop_evdev:error on device(%s:%d), killing\n",d->devnode,d->fd);
    lwl_evdev_kill(d);
  }else if(e->events&(EPOLLIN)){
    //TODO:manage evdev reads
  }
}

static void loop_client(struct epoll_event *e)
{
  struct client *c=&clients[e->data._32];
  if(e->events&(EPOLLERR|EPOLLHUP)){
    client_kill(c);
  }else if(e->events&(EPOLLIN)){
    if(client_rd(c)<0) client_kill(c);
  }
}

static void loop_udev(struct epoll_event *e)
{
  if(e->events&(EPOLLERR|EPOLLHUP)){
    PERRC("fatal:udev monitor socket epolling failure\n");
    exit(LWL_ERR);
  }else if(e->events&(EPOLLIN)) lwl_udev_monitor_rd();
}

#define EVTS_N (3+CLIENTS_MAX+LINUX_EVDEVS_MAX)//kind of arbitrary
static void loop(void)
{
  while(1){
    static struct epoll_event evts[EVTS_N];
    l r;
    while(1){
      memset(evts,0,sizeof(evts));
      r=epoll_wait(ep_fd,evts,EVTS_N,-1);
      if(r!=-EINTR) break;
    }
    if(ISERR(r)){
      PERR("fatal(%ld):error epolling fds\n",r);
      exit(LWL_ERR);
    }
    l j=0;
    while(1){
      if(j==r) break;
      struct epoll_event *e=&evts[j];
           if(e->data._32==EPOLL_SIGS) loop_sigs(e);
      else if(e->data._32==EPOLL_SRV) loop_srv(e);
      else if(e->data._32==EPOLL_UDEV) loop_udev(e);
      else if(CLIENTS_MAX<=e->data._32&&e->data._32<EPOLL_SIGS) loop_evdev(e);
      else if(e->data._32<CLIENTS_MAX) loop_client(e);
      ++j;
    }
  }
}

//******************************************************************************
void ulinux_start(l argc,void **argv_envp)
{
#ifndef QUIET 
  static u8 dprintf_buf[DPRINTF_BUF_SZ];
  g_dprintf_buf=dprintf_buf;
#endif
  sigs_setup();
  output_layout_row_init();
  srv_cfg_file(argc,argv_envp);
  srv_so_setup(argc,argv_envp);
  epoll_setup();
  lwl_udev_init(ep_fd);
  clients_init(ep_fd);
  registry_init();
  lwl_evdevs_init(ep_fd);
  lwl_udev_input_cold_plug();
  loop();
  exit_ok();
}
